package org.dandsoft.blackbox.utils.table;

import org.dandsoft.blackbox.utils.list.ListElement;
import org.dandsoft.blackbox.utils.list.ListElementIterator;

import java.util.Map;
import java.util.Hashtable;

/**
 * @author Vilyuzhanin Denis (Fin)
 */
public class LiveTable {

    public final static long FUNCTIONS_COLUMN_KEY = 0;
    private Row firstRow = new Row();
    private Object object;
    private Map<ColumnKey, Column> columnsIndex = new Hashtable<ColumnKey, Column>();
    private Map<Long, Row> rowsIndex = new Hashtable<Long, Row>();
    private Map<CellKey, Cell> cells = new Hashtable<CellKey, Cell>();
    private Column functionColumn = new Column(FUNCTIONS_COLUMN_KEY);
    private Column firstArgumentsColumn = new Column();
    private Column firstResultsColumn = new Column();


    /**
     * Установить объект таблицы
     *
     * @param object
     */
    public void setTableObject(Object object) {
        this.object = object;
    }

    /**
     * Получить объект таблицы
     *
     * @return
     */
    public Object getTableObject() {
        return this.object;
    }

    /**
     * Установить значение в ячейку аргументов
     *
     * @param rowKey
     * @param columnKey
     * @param value
     */
    public void setArgumentCellValue(long rowKey, long columnKey, Object value) {
        CellKey key = CellKey.createArgumentCellKey(rowKey, columnKey);
        Cell cell = cells.get(key);
        if (cell == null && value != null) {
            cell = new Cell();
            cell.setData(value);
            cells.put(key, cell);

            Row row = findRow(rowKey, true);
            Column column = findColumn(new ColumnKey(columnKey, true), true);
            row.addArgumentCell(cell);
            column.addCell(cell);
            cell.setRowKey(row.getRowKey());
            cell.setColumnKey(column.getColumnKey());
        } else if (cell != null && value == null) {
            cells.remove(key);
            cell.disconnect();
        } else if (cell != null && value != null) {
            cell.setData(value);
        }
    }

    /**
     * Получить значение с ячейки аргументов
     *
     * @param rowKey
     * @param columnKey
     * @return
     */
    public Object getArgumentCellValue(long rowKey, long columnKey) {
        CellKey key = CellKey.createArgumentCellKey(rowKey, columnKey);

        Cell cell = cells.get(key);
        if (cell != null) {
            return cell.getData();
        } else {
            return null;
        }
    }

    /**
     * Получить итератор значение для получения значений колонки
     *
     * @param columnKey
     * @return
     */
    public CellValueIterator getArgumentsColumnCellIterator(long columnKey) {
        Column column = findColumn(new ColumnKey(columnKey, true), false);
        if (column != null) {
            ListElementIterator<Cell> iterator = column.getFirstCell().getColumnListIterator();
            iterator.nextRight();
            return new CellIterator(iterator);
        } else {
            return null;
        }
    }

    /**
     * Получить итератор значений, для получения значений, колонки результатов
     *
     * @param columnKey
     * @return
     */
    public CellValueIterator getResultColumnCellIterator(long columnKey) {
        Column column = findColumn(new ColumnKey(columnKey, false), false);
        if (column != null) {
            ListElementIterator<Cell> iterator = column.getFirstCell().getColumnListIterator();
            iterator.nextRight();
            return new CellIterator(iterator);
        } else {
            return null;
        }
    }

    /**
     * Получить итератор значений для получения значений строки аргуменов
     *
     * @param rowKey
     * @return
     */
    public CellValueIterator getArgumentsRowCellIterator(long rowKey) {
        Row row = findRow(rowKey, false);
        if (row != null) {
            ListElementIterator<Cell> iterator = row.getFirstArgumentsCell().getRowListIterator();
            iterator.nextRight();
            return new CellIterator(iterator);
        } else {
            return new CellIterator(null);
        }
    }

    /**
     * Получить итератор значений для получения значений строки результатов.
     *
     * @param rowKey Ключ строки
     * @return
     */
    public CellValueIterator getResultRowCellIterator(long rowKey) {
        Row row = findRow(rowKey, false);
        if (row != null) {
            ListElementIterator<Cell> iterator = row.getFirstResultsCell().getRowListIterator();
            iterator.nextRight();
            return new CellIterator(iterator);
        } else {
            return new CellIterator(null);
        }
    }

    /**
     * Получить итератор значений в колонке функций
     *
     * @return
     */
    public CellValueIterator getFunctionColumnCellIterator() {
        ListElementIterator<Cell> iterator = functionColumn.getFirstCell().getColumnListIterator();
        iterator.nextRight();
        return new CellIterator(iterator);
    }


    /**
     * Установить значение ячейки результата
     *
     * @param rowKey
     * @param columnKey
     * @param value
     */
    public void setResultCellValue(long rowKey, long columnKey, Object value) {
        CellKey key = CellKey.createResultCellKey(rowKey, columnKey);
        Cell cell = cells.get(key);
        if (cell == null && value != null) {
            cell = new Cell();
            cell.setData(value);
            cells.put(key, cell);

            Row row = findRow(rowKey, true);
            Column column = findColumn(new ColumnKey(columnKey, false), true);
            row.addResultCell(cell);
            column.addCell(cell);
            cell.setRowKey(row.getRowKey());
            cell.setColumnKey(column.getColumnKey());
        } else if (cell != null && value == null) {
            cells.remove(key);
            cell.disconnect();
        } else if (cell != null && value != null) {
            cell.setData(value);
        }
    }

    /**
     * Установить в заголовок колонки результатов значение
     *
     * @param columnKey ключ колонки
     * @param value     значение
     */
    public void setResultCellHeaderValue(long columnKey, Object value) {
        Column column = findColumn(new ColumnKey(columnKey, false), true);
        column.setData(value);
    }

    /**
     * Получить из заголовка колонки результатов значение
     *
     * @param columnKey Ключ заголовка колонки
     * @return
     */
    public Object getResultCellHeaderValue(long columnKey) {
        Column column = findColumn(new ColumnKey(columnKey, false), false);
        if (column != null) {
            return column.getData();
        } else {
            return null;
        }
    }


    /**
     * Установить в заголовок колонки аргументов значение
     *
     * @param columnKey ключ колонки
     * @param value     значение
     */
    public void setArgumentCellHeaderValue(long columnKey, Object value) {
        Column column = findColumn(new ColumnKey(columnKey, true), true);
        column.setData(value);
    }

    /**
     * Получить из заголовка колонки аргументов значение
     *
     * @param columnKey Ключ заголовка колонки
     * @return
     */
    public Object getArgumentCellHeaderValue(long columnKey) {
        Column column = findColumn(new ColumnKey(columnKey, true), false);
        if (column != null) {
            return column.getData();
        } else {
            return null;
        }
    }


    /**
     * Получить значение ячейки результата
     *
     * @param rowKey
     * @param columnKey
     * @return
     */
    public Object getResultCellValue(long rowKey, long columnKey) {
        CellKey key = CellKey.createResultCellKey(rowKey, columnKey);
        Cell cell = cells.get(key);
        if (cell != null) {
            return cell.getData();
        } else {
            return null;
        }
    }

    /**
     * Установить значение в ячейку колонки функций
     *
     * @param rowKey
     * @param value
     */
    public void setFunctionCellValue(long rowKey, Object value) {
        CellKey key = CellKey.createFunctionCellKey(rowKey);
        Cell cell = cells.get(key);
        if (cell == null && value != null) {
            cell = new Cell();
            cell.setData(value);
            cells.put(key, cell);

            Row row = findRow(rowKey, true);
            row.addResultCell(cell);
            functionColumn.addCell(cell);
            cell.setRowKey(row.getRowKey());
            cell.setColumnKey(functionColumn.getColumnKey());
        } else if (cell != null && value == null) {
            cells.remove(key);
            cell.disconnect();
        } else if (cell != null && value != null) {
            cell.setData(value);
        }
    }

    /**
     * Получить значение ячейки в колонки функций
     *
     * @param rowKey Номер ряда в колонке
     * @param <Data> Тип получаемой значений который сохранен в колонке
     * @return Значение в колонке или null если там нет значений
     * @throws ClassCastException Если тип в ячейке не совместим с типов Data
     */
    public <Data> Data getFunctionCellValue(long rowKey) {
        CellKey key = CellKey.createFunctionCellKey(rowKey);
        Cell cell = cells.get(key);
        if (cell != null) {
            return (Data) cell.getData();
        } else {
            return null;
        }
    }

    /**
     * Итератор строк таблицы
     *
     * @return
     */
    public RowIterator getRowIterator() {
        ListElementIterator<Row> iterator = new ListElementIterator<Row>(firstRow.getRowList());
        iterator.nextRight();
        return new RowIteratorImpl(iterator);
    }

    /**
     * Удалить строку из таблицы. Удаление происходит во всех группах
     *
     * @param rowKey
     */
    public void removeRow(long rowKey) {
        Row row = rowsIndex.get(rowKey);
        if (row != null) {
            doClearResultsRow(row);
            doClearArgumentsRow(row);
            doClearFunctionRow(row);
            row.getRowList().remove();
        }
    }

    /**
     * Удалить колонку аргументов
     * @param columnKey
     */
    public void removeArgumentColumn(long columnKey) {
        Column column = columnsIndex.get(new ColumnKey(columnKey, true));
        if (column != null) {
            doClearArgumentColumn(column);
            column.getColumnList().remove();
        }
    }

    /**
     * Удалить колонку результатов
     * @param columnKey
     */
    public void removeResultColumn(long columnKey) {
        Column column = columnsIndex.get(new ColumnKey(columnKey, false));
        if (column != null) {
            doClearResultColumn(column);
            column.getColumnList().remove();
        }
    }

    /**
     * Удалить колонку функций
     */
    public void removeFunctionColumn() {
        doClearFunctionColumn();
    }


    /**
     * Выполнить отчистку строки в колонках результата
     *
     * @param row
     */
    private void doClearResultsRow(Row row) {
        Cell cell = row.getFirstResultsCell();
        ListElementIterator<Cell> iterator = cell.getRowListIterator();
        iterator.nextRight();
        Cell previousCell = null;
        while (iterator.hasNextRight()) {
            ListElement<Cell> currentCellElement = iterator.nextRight();
            if (previousCell != null) {
                previousCell.disconnect();
                cells.remove(CellKey.createResultCellKey(cell.getRowKey(), cell.getColumnKey()));
            }
            previousCell = currentCellElement.getElement();
        }
        cells.remove(CellKey.createResultCellKey(cell.getRowKey(), cell.getColumnKey()));
        rowsIndex.remove(row.getRowKey());
    }

    /**
     * Выполнить отчистку строки в колонках аргументов
     *
     * @param row
     */
    private void doClearArgumentsRow(Row row) {
        Cell cell = row.getFirstArgumentsCell();
        ListElementIterator<Cell> iterator = cell.getRowListIterator();
        iterator.nextRight();
        Cell previousCell = null;
        while (iterator.hasNextRight()) {
            ListElement<Cell> currentCellElement = iterator.nextRight();
            if (previousCell != null) {
                previousCell.disconnect();
                cells.remove(CellKey.createArgumentCellKey(cell.getRowKey(), cell.getColumnKey()));
            }
            previousCell = currentCellElement.getElement();
        }
        cells.remove(CellKey.createArgumentCellKey(cell.getRowKey(), cell.getColumnKey()));
        rowsIndex.remove(row.getRowKey());
    }

    /**
     * Выполнить отчистку в строке колонки функций
     *
     * @param row
     */
    private void doClearFunctionRow(Row row) {
        Cell cell = row.getFunctionCell();
        if (cell != null) {
            cells.remove(CellKey.createFunctionCellKey(cell.getRowKey()));
        }
        row.setFunctionCell(null);
    }

    /**
     * Удаляет всю колонку аргументов
     *
     * @param column
     */
    private void doClearArgumentColumn(Column column) {
        Cell cell = column.getFirstCell();
        ListElementIterator<Cell> iterator = cell.getColumnListIterator();
        iterator.nextRight();
        Cell previousCell = null;
        while (iterator.hasNextRight()) {
            ListElement<Cell> currentCellElement = iterator.nextRight();
            if (previousCell != null) {
                previousCell.disconnect();
                cells.remove(CellKey.createArgumentCellKey(cell.getRowKey(), cell.getColumnKey()));
            }
            previousCell = currentCellElement.getElement();
        }
        cells.remove(CellKey.createArgumentCellKey(cell.getRowKey(), cell.getColumnKey()));
        columnsIndex.remove(new ColumnKey(column.getColumnKey(), true));

    }

    /**
     * Удаляет всю колонку результата
     *
     * @param column
     */
    private void doClearResultColumn(Column column) {
        Cell cell = column.getFirstCell();
        ListElementIterator<Cell> iterator = cell.getColumnListIterator();
        iterator.nextRight();
        Cell previousCell = null;
        while (iterator.hasNextRight()) {
            ListElement<Cell> currentCellElement = iterator.nextRight();
            if (previousCell != null) {
                previousCell.disconnect();
                cells.remove(CellKey.createResultCellKey(cell.getRowKey(), cell.getColumnKey()));
            }
            previousCell = currentCellElement.getElement();
        }
        cells.remove(CellKey.createArgumentCellKey(cell.getRowKey(), cell.getColumnKey()));
        columnsIndex.remove(new ColumnKey(column.getColumnKey(), false));

    }

    /**
     * Удаляет всю колонку функций
     */
    private void doClearFunctionColumn() {
        Cell cell = functionColumn.getFirstCell();
        ListElementIterator<Cell> iterator = cell.getColumnListIterator();
        iterator.nextRight();
        Cell previousCell = null;
        while (iterator.hasNextRight()) {
            ListElement<Cell> currentCellElement = iterator.nextRight();
            if (previousCell != null) {
                previousCell.disconnect();
                cells.remove(CellKey.createResultCellKey(cell.getRowKey(), cell.getColumnKey()));
            }
            previousCell = currentCellElement.getElement();
        }
        cells.remove(CellKey.createArgumentCellKey(cell.getRowKey(), cell.getColumnKey()));
        functionColumn = null;
    }

    /**
     * Найти строку
     *
     * @param rowKey
     * @param isCreate
     * @return
     */
    private Row findRow(long rowKey, boolean isCreate) {
        Row row;
        synchronized (rowsIndex) {
            row = rowsIndex.get(rowKey);
            if (row == null && isCreate) {
                row = new Row();
                row.setRowKey(rowKey);
                rowsIndex.put(rowKey, row);
                firstRow.getRowList().connectOnRight(row.getRowList());
            } else if (row == null && !isCreate) {
                return null;
            }
        }
        return row;
    }

    /**
     * Найти колонку
     *
     * @param columnKey
     * @param isCreate
     * @return
     */
    private Column findColumn(ColumnKey columnKey, boolean isCreate) {
        Column column;
        synchronized (columnsIndex) {
            column = columnsIndex.get(columnKey);
            if (column == null && isCreate) {
                column = new Column();
                column.setColumnKey(columnKey.getColumnKey());
                columnsIndex.put(columnKey, column);
                if (columnKey.isArgument) {
                    firstArgumentsColumn.getColumnList().connectUnder(column.getColumnList());
                } else {
                    firstResultsColumn.getColumnList().connectUnder(column.getColumnList());
                }
            } else if (column == null && !isCreate) {
                return null;
            }
            return column;
        }
    }

    public long getRowCount() {
        return 0;
    }

    public long getColumnCount() {
        return 0;
    }

    public LongKeyIterator getColumnsKeyIterator() {
        return null;
    }

    public LongKeyIterator getRowKeyIterator() {
        return null;
    }


    /**
     * Класс производится движение по ячейкам таблицы
     */
    class CellIterator implements CellValueIterator {
        private ListElementIterator<Cell> iterator;
        private Cell current;

        CellIterator(ListElementIterator<Cell> iterator) {
            this.iterator = iterator;
        }

        public boolean hasNext() {
            return iterator != null && iterator.hasNextRight();
        }

        public <Data> Data next() {
            if (iterator != null) {
                current = iterator.nextRight().getElement();
                if (current != null) {
                    return (Data) current.getData();
                } else {
                    return null;
                }
            } else {
                return null;
            }
        }

        public long getColumnKey() {
            if (current != null) {
                return current.getColumnKey();
            } else {
                return -1;
            }
        }

        public long getRowKey() {
            if (current != null) {
                return current.getRowKey();
            } else {
                return -1;
            }
        }
    }


    /**
     * Производит движение по строкам таблицы
     */
    static class RowIteratorImpl implements RowIterator {
        private ListElementIterator<Row> iterator;
        private Row currentRow;

        RowIteratorImpl(ListElementIterator<Row> iterator) {
            this.iterator = iterator;
        }

        public boolean hasNext() {
            return iterator.hasNextRight();
        }

        public boolean next() {
            if (iterator.hasNextRight()) {
                currentRow = iterator.nextRight().getElement();
                return currentRow != null;
            } else {
                return false;
            }
        }

        public long getRowKey() {
            if (currentRow != null) {
                return currentRow.getRowKey();
            } else {
                return Long.MIN_VALUE;
            }
        }

        public Object getValue() {
            if (currentRow != null) {
                return currentRow.getData();
            } else {
                return null;
            }
        }
    }

    /**
     * Применяется для идентификации ячеек во всех областях. Значений, Функции и Резулятата
     */
    static class CellKey {
        private long rowKey;
        private long columnKey;
        private boolean isArgument;
        private boolean isFunction;

        public static CellKey createArgumentCellKey(long rowKey, long columnKey) {
            CellKey key = new CellKey();
            key.rowKey = rowKey;
            key.columnKey = columnKey;
            key.isArgument = true;
            key.isFunction = false;
            return key;
        }

        public static CellKey createResultCellKey(long rowKey, long columnKey) {
            CellKey key = new CellKey();
            key.rowKey = rowKey;
            key.columnKey = columnKey;
            key.isArgument = false;
            key.isFunction = false;
            return key;
        }

        public static CellKey createFunctionCellKey(long rowKey) {
            CellKey key = new CellKey();
            key.rowKey = rowKey;
            key.columnKey = -1;
            key.isArgument = false;
            key.isFunction = true;
            return key;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            CellKey cellKey = (CellKey) o;

            if (columnKey != cellKey.columnKey) return false;
            if (isArgument != cellKey.isArgument) return false;
            if (isFunction != cellKey.isFunction) return false;
            if (rowKey != cellKey.rowKey) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = (int) (rowKey ^ (rowKey >>> 32));
            result = 31 * result + (int) (columnKey ^ (columnKey >>> 32));
            result = 31 * result + (isArgument ? 1 : 0);
            result = 31 * result + (isFunction ? 1 : 0);
            return result;
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder();
            sb.append("CellKey");
            sb.append("{rowKey=").append(rowKey);
            sb.append(", columnKey=").append(columnKey);
            sb.append(", isArgument=").append(isArgument);
            sb.append(", isFunction=").append(isFunction);
            sb.append('}');
            return sb.toString();
        }
    }

    static class ColumnKey {
        private long columnKey;
        private boolean isArgument;

        ColumnKey(long columnKey, boolean argument) {
            this.columnKey = columnKey;
            isArgument = argument;
        }

        public long getColumnKey() {
            return columnKey;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            ColumnKey columnKey = (ColumnKey) o;

            if (this.columnKey != columnKey.columnKey) return false;
            if (isArgument != columnKey.isArgument) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = (int) (columnKey ^ (columnKey >>> 32));
            result = 31 * result + (isArgument ? 1 : 0);
            return result;
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder();
            sb.append("ColumnKey");
            sb.append("{columnKey=").append(columnKey);
            sb.append(", isArgument=").append(isArgument);
            sb.append('}');
            return sb.toString();
        }
    }


}
